package msgcomm.transceivers;

import java.io.FileNotFoundException;
import java.util.LinkedList;
import java.util.Queue;

import msgcomm.Hooks;
import msgcomm.Receiver;
import msgcomm.messages.AreYouAlive;
import msgcomm.messages.Message;
import msgcomm.messages.NormalPayload;
import msgcomm.messages.Payload;
import msgcomm.messages.RequestPayload;
import msgcomm.messages.RequestRefreshName;

import cluedo.messages.RequestRefreshPlayGroup;


import common.Logger;



/**
 * E' la classe base con cui e' possibile inviare e ricevere messaggi.
 * Implementa solo il meccanismo per le richieste bloccanti.
 * Estendendo questa classe e' possibile fornire altri tipi di servizi
 * 
 * Chi implementa questa classe deve implementare:
 * <ol>
 *    <li>Receiver.receive()
 *    <li>un metodo per inviare messaggi (es. send() o multicast())
 *    <li>un metodo che consegna il messaggio, ovvero lo mette nella coda dei 
 *    messaggi (es. multicastDeliver())
 * </ol>
 */
public abstract class AbstractTransceiver implements Receiver {

	//private VectorClock clock;
	
	/**
	 * Logger
	 */
	protected Logger log;
	
	/**
	 * Nome identificativo di questro transceiver
	 */
	protected final String name;
	
	/**
	 * Dimensione della coda dei messaggi
	 */
	protected final int MSG_QUEUE_SIZE= 10;
	/**
	 * Coda dei messaggi ricevuti e ordinati secondo qualche politica.
	 * Questi messaggi devono solo essere prelevati dall'utente con <code>getNextMessage()</code>
	 */
	protected Queue<Message> msgQueue;
	
	/**
	 * Listener per le richieste bloccanti
	 */
	protected Hooks hooks;
	
	// COSTRUTTORI
	
	/**
	 * Crea un nuovo transceiver. Non crea una nuova coda, ma usa quella data.
	 * @param name nome del transceiver
	 * @param msgQueue coda dei messaggi da utilizzare
	 * @param reqList request listener
	 */
	protected AbstractTransceiver(String name, Queue<Message> msgQueue, Hooks hooks) {
		this.name= name;
		this.log= new Logger();
		
		// coda dei messaggi vuota
		this.msgQueue= msgQueue;
		this.msgQueue.clear();

		this.hooks= hooks;
	}
	
	/**
	 * Crea un nuovo transceiver. Non crea una nuova coda, ma usa quella data.
	 * @param name nome del transceiver
	 * @param msgQueue coda dei messaggi da utilizzare
	 */
	protected AbstractTransceiver(String name, Queue<Message> msgQueue) {
		this(name, msgQueue, null);
	}
	
	/**
	 * Crea un nuovo transceiver
	 * @param name nome del transceiver
	 */
	public AbstractTransceiver(String name) {
		this(name, new LinkedList<Message>(), null);
	}
	
	/**
	 * Crea un nuovo transceiver
	 * @param name nome del transceiver
	 * @param reqList request listener
	 */
	public AbstractTransceiver(String name, Hooks reqList) {
		this(name, new LinkedList<Message>(), reqList);
	}
	
	public void setLogger(Logger log) {
		this.log=log;
	}
	
	// REQUEST LISTENER
	
	public void setHooks(Hooks hooks) {
		this.hooks= hooks;
	}
	
	public void removeHooks() {
		hooks= null;
	}
	
	// CODA DEI MESSAGGI
	
	/**
	 * Recupero un messaggio dalla coda dei messaggi (chiamata bloccante).
	 * Questo metodo e' il consumatore dei messaggi della coda.
	 * @return il messaggio da consegnare
	 * @throws InterruptedException 
	 */
	public Message getNextMessage() throws InterruptedException {
		synchronized (msgQueue) {
			
			boolean cont= true;
			Message msg= null;
			while (cont) {
				
				// aspetto fintanto che la coda e' vuota
				while(msgQueue.size()==0) {
					try {
						msgQueue.wait();
					} catch (InterruptedException e) {
						// bisogna fermare il thread
						Thread.currentThread().interrupt();
						throw e;
					}
				}
				
				// verifico se il msg in testa ha un NormalPayload
				msg= msgQueue.element();
				Payload pl= msg.getPayload();

				if (pl instanceof NormalPayload) {
					msg= msgQueue.remove(); // rimozione in testa
					if (!(pl instanceof AreYouAlive)) {
						log.printLogTh("at-getNextMessage " + msg + ": rimosso dalla coda e consegnato al chiamante");
					}
					cont=false;
				} else {
					try {
						// il messaggio di testa non e' un NormalPaylod: aspetto che venga rimosso
						msgQueue.wait();
					} catch (InterruptedException e) {
						// bisogna fermare il thread
						Thread.currentThread().interrupt();
						throw e;
					}
				}
			}
			
			msgQueue.notifyAll(); // notifico che ho rimosso un messaggio
			return msg;
		}
	}
	


	/**
	 * Consegna il messaggio. La consegna e' effettuata ponendo il messaggio nella
	 * coda dei messaggi, dai cui puo' essere prelevato con <code>getNextMessage()</code>.
	 * Questo metodo e' il produttore di messaggi nella coda.
	 * @param msg messaggio da consegnare
	 */
	protected void putMessage(Message msg) {
		synchronized (msgQueue) {
			while (msgQueue.size()>MSG_QUEUE_SIZE) {
				try {
					msgQueue.wait();
				} catch (InterruptedException e) {
					// TODO corretto?
					System.err.println("wait interrotto (non e' un problema): " +e.getMessage());
				}
			}
			if (! (msg.getPayload() instanceof AreYouAlive)) {
				log.printTh("at-putMessage, aggiunto in coda " + msg);
			}
			msgQueue.add(msg); // aggiungo in coda
			msgQueue.notifyAll();
		}
	}
	
	// RICHIESTE BLOCCANTI
	
	/**
	 * Invia una richiesta bloccante.
	 * Nota che non e' supportato l'invio di richieste a se stessi
	 * @param destName nome del destinatario
	 * @param dest destinatario
	 * @param data payload del messaggio
	 * @return messaggio di risposta
	 * @throws Exception
	 */
	public Message blockingRequest(String destName, Receiver dest, RequestPayload data, boolean silent) throws Exception {
		
		Message msg = new Message(name,data);
		
		Message resp=null;
		
		if (!silent) log.printTh("at-blockingRequest " + msg + ": sto per inviare a \"" + destName + "\"");
		resp= dest.blockingReceive(msg);
		if (!silent) log.printTh("at-blockingRequest " + msg + ": ricevuta risposta " + resp + " da \"" + destName + "\"");
		return resp;
	}
	
	public Message blockingRequest(String destName, Receiver dest, RequestPayload data) throws Exception {
		return blockingRequest(destName, dest, data, false);
	}
	
	@Override
	public Message blockingReceive(Message msg) {
		// NB: blockingReceive coincide con blockingDeliver
		Payload myData= msg.getPayload();
		
		synchronized (msgQueue) {
			while (msgQueue.size()>MSG_QUEUE_SIZE) {
				try {
					//printDebug("In attesa che la coda si svuoti un po'");
					msgQueue.wait();
				} catch (InterruptedException e) {
					// TODO corretto?
					System.err.println("wait interrotto (non e' un problema): " +e.getMessage());
				}
			}

			if ( !(myData instanceof RequestRefreshPlayGroup) && !(myData instanceof RequestRefreshName) ) {
				// TODO che bruttura! sto usando una classe appartenente al contensto di cluedo!
				log.printTh("at-blockingReceive " + msg + ", aggiungo in coda");
			}
			msgQueue.add(msg); // aggiungo in coda
			
			boolean cont= true;
			Message resp= null;
			while (cont) {
				Message mq= msgQueue.element();
				Payload pl= mq.getPayload();
				
				if (pl instanceof RequestPayload && mq.equals(msg)) {
					resp= hooks.respondToRequest(msg);
					if ( !(myData instanceof RequestRefreshPlayGroup) && !(myData instanceof RequestRefreshName) ) {
						// TODO che bruttura! sto usando una classe appartenente al contensto di cluedo!
						log.printLogTh("at-blockingReceive " + msg + ", risposta del request listener: " + resp);
					}
					msgQueue.remove();
					cont= false;
				} else {
					try {
						msgQueue.wait();
					} catch (InterruptedException e) {
						// TODO corretto?
						System.err.println("wait interrotto (non e' un problema): " +e.getMessage());
					}
				}
			}
			
			msgQueue.notifyAll();
			
			return resp;
		}
	}
	
	/**
	 * Invia il messagio al ricevente attraverso il metodo "receive&deliver"
	 * @throws Exception 
	 */
	public void sendAndDeliver(String destName, Receiver dest, NormalPayload data) throws Exception {
		Message msg= new Message(name, data);
		if (!(data instanceof AreYouAlive)) log.printLogTh("at-sendAndDeliver " + msg);
		dest.receiveAndDeliver(msg);
	}

	@Override
	public void receiveAndDeliver(Message msg) {
		if (!(msg.getPayload() instanceof AreYouAlive)) log.printLogTh("at-receiveAndDeliver " + msg);
		putMessage(msg);
	}
	
}
